/*
 * Copyright (c) 2003
 *	The University of Guelph.  All rights reserved.
 *
 * This software was written by Rick Macklem and was derived in part from
 * software in various BSD releases covered by the
 * subsequent copyright notices.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * It is also requested that vendors of products using all or part of this
 * software acknowledge that their products are based upon open source
 * written by Rick Macklem at the University of Guelph.
 *
 * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF GUELPH ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE UNIVERSITY OF GUELPH BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Handling of GSS tokens for NFS.
 */
#ifndef APPLEKEXT
#include <newnfs/nfs/nfsport.h>

extern struct nfsstats newnfsstats;
extern int nfsrv_debug;
extern time_t nfsrvboottime;
NFSGSSHANDLEMUTEX;
NFSGSSCLMUTEX;
NFSGSSCLHANDLEMUTEX;
NFSREQSPINLOCK;
LIST_HEAD(nfsgsshash, nfsgss_handle) nfsgsshashtbl[NFSGSSCACHE_HASHSIZE];
TAILQ_HEAD(nfsgsslru, nfsgss_handle) nfsgsslruhead;
struct nfsgssclsockhead nfsgssclsockhead;
int nfsgss_highwater = NFSGSS_HIGHWATER;
int nfsgss_numgssd = 0;
u_int32_t nfsgsscl_cnt = 0;
u_int32_t nfsgsscl_highwater = NFSGSSCLHIGHWATER;
u_int32_t nfsgsscl_seqmisscnt = 0;

/*
 * This list defines the GSS mechanisms supported.
 * (Don't ask me how you get these strings from the RFC stuff like
 *  iso(1), org(3)... but someone did it, so I don't need to know.)
 */
struct nfsgss_mechlist nfsgss_mechlist[] = {
	{ 9, "\052\206\110\206\367\022\001\002\002", 11 },
	{ 0, "", 0 },
};
#endif	/* !APPLEKEXT */

/*
 * GSS context cache stuff. (server side)
 */
#define	NFSGSSHASH(p, l) \
	(&nfsgsshashtbl[nfsgss_hash((p), (l)) % NFSGSSCACHE_HASHSIZE])
static int nfsgss_cachesize = 0;
static struct nfssockreq nfsgss_sock[NFSGSS_MAXGSSD];

/* local functions */
static void nfsgsscl_trim(struct nfsmount *);
static int nfsgss_getclhandle(struct nfsreq *, u_char *, int, uid_t, u_int32_t,
    int);
static int nfsgss_cktoken(struct nfsrv_descript *nd, int *toklenp, int toktype,
    int *mechp, u_char *seqnum, u_char *cksum);
static int nfsgss_findmech(u_char *cp, int len);
static void nfsgss_calcverf(int mechflag, KEYSCHED_T sched, u_int32_t seqnum,
    u_char *calcsum);
static int nfsgss_putlen(struct nfsrv_descript *nd, int len);
static int nfsgss_calcputlen(int len);
static void nfsgss_destroyhandle(struct nfsgss_handle *gssp, NFSPROC_T *p);
static void nfsgss_trimcache(NFSPROC_T *p);
static void nfsgss_cleanincomplete(NFSPROC_T *p);
static void nfsgss_newgsshandle(struct nfsgss_handle *gssp);
static int nfsgss_hash(u_char *cp, int len);
static int nfsgss_continitdestroy(struct nfsgss_handle *gssp, NFSPROC_T *p);
static int nfsgss_nextclseqnum(struct nfsreq *rep, u_int32_t *seqnump);
#ifdef notnow
static void nfsgss_cleancache(NFSPROC_T *p);
#endif

#define nfsgssclhash(u)		(((u) + ((u) >> 8)) % NFSGSSCLHASHSIZE)

/*
 * First 8 bytes of the Kerberos V innerContextToken
 * (The first refers to a GSS_GetMIC() with DES MAC MD5 signing alg.
 *  The second refers to a GSS_Wrap() with DES MAC MD5 signing and
 *  DES data encryption.)
 * For both, these 8 bytes are followed by an 8 byte sequence number
 * and an 8 byte checksum.
 */
static u_char kerbv_micstr[8] = { 0x1, 0x1, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff };
static u_char kerbv_wrapstr[8] = { 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff };

/*
 * Zero ivec for des.
 */
static u_char ivec0[8] = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };

/*
 * The function does the checksum for an RPC header with RPCAUTH_GSS
 * authentication flavour. The checksum algorithm is dependent upon
 * the underlying mechanism used, as specified in the GSS token.
 * direction == 0 --> received from initiator
 * direction == 1 --> received from acceptor
 */
APPLESTATIC int
nfsgss_ckheader(struct nfsrv_descript *nd, int toklen, int cklen,
    int direction, struct nfsgss_handleref *handp, KEYSCHED_T sched)
{
	int mech, error, totlen;
	u_char seqnum[8], cksum[8], calcsum[8];

	/*
	 * Fail if the context isn't complete.
	 */
	if (!(handp->nfsh_flag & NFSG_COMPLETE))
		return (AUTH_PROBCRED);

	/*
	 * Check the token and get the mech, cksum and seqnum.
	 */
	totlen = NFSM_RNDUP(toklen);
	error = nfsgss_cktoken(nd,&totlen,RPCAUTHGSS_MIC,&mech,seqnum,cksum);
DEBUG2PRINTF("aft cktoken err=%d totlen=%d\n",error,totlen);
	if (!error && totlen > 0)
		error = nfsm_advance(nd, totlen, -1);
	if (error)
		return (error);

	/*
	 * Calculate the checksum.
	 */
	switch (mech) {
	case KERBV_MECH:
		nd->nd_flag |= ND_KERBV;
		nfsgss_desmacmd5(nd, cklen, sched, RPCAUTHGSS_MIC, calcsum);
DEBUG2PRINTF("ckheader aft desmac\n");
		if (NFSBCMP(calcsum, cksum, 8))
			return (AUTH_PROBCRED);
DEBUG2PRINTF("ckheader at decrypt\n");
		/*
		 * Just check direction indicator, since there is a sequence#
		 * in the AUTH_GSS header and we can't enforce sequencing here.
		 */
		CBCMODE((const KEY_T *)seqnum, (KEY_T *)seqnum, 8, sched,
		    cksum, 0);
		if ((direction == 0 &&
		     (seqnum[4] != 0x0 || seqnum[5] != 0x0 ||
		      seqnum[6] != 0x0 || seqnum[7] != 0x0)) ||
		    (direction != 0 &&
		     (seqnum[4] != 0xff || seqnum[5] != 0xff ||
		      seqnum[6] != 0xff || seqnum[7] != 0xff)))
			return (AUTH_PROBCRED);
		break;
	default:
		return (AUTH_BADCRED);
	};
	return (0);
}

/*
 * Check a GSS token and get the seqnum and cksum.
 */
static int
nfsgss_cktoken(struct nfsrv_descript *nd, int *toklenp, int toktype,
    int *mechp, u_char *seqnum, u_char *cksum)
{
	u_char *cp;
	int i;
	int error = 0, toklen = *toklenp;

	/*
	 * Now, rattle through the gss token to find out the mechanism
	 * and get the checksum, etc.
	 */
	if (toklen < 4)
		return (AUTH_BADCRED);
	toklen -= 4;
	NFSM_DISSECT(cp, u_char *, 2);
	if (*cp++ != 0x60)
		return (AUTH_BADCRED);
DEBUG2PRINTF("rcv siz0 0x%x\n",*cp);
	if (*cp & 0x80) {
		i = (*cp & ~0x80);	/* Number of additional size bytes */
		NFSM_DISSECT(cp, u_char *, i);
		toklen -= i;
	}
	NFSM_DISSECT(cp, u_char *, 2);
	if (*cp++ != 0x6)
		return (AUTH_BADCRED);
	i = *cp;
	if (i < 1 || i > 127)
		return (AUTH_BADCRED);
	toklen -= i;
	NFSM_DISSECT(cp, u_char *, i);
	*mechp = nfsgss_findmech(cp, i);
	if (*mechp == KERBV_MECH) {
		if (toklen < GSSX_KERBVTOKEN)
			return (AUTH_BADCRED);
		toklen -= GSSX_KERBVTOKEN;
		NFSM_DISSECT(cp, u_char *, 8);
		if (toktype == RPCAUTHGSS_MIC) {
			if (NFSBCMP(cp, kerbv_micstr, 8))
				return (AUTH_BADCRED);
		} else if (toktype == RPCAUTHGSS_WRAP) {
			if (NFSBCMP(cp, kerbv_wrapstr, 8))
				return (AUTH_BADCRED);
		}
		NFSM_DISSECT(cp, u_char *, 8);
		NFSBCOPY(cp, seqnum, 8);
		NFSM_DISSECT(cp, u_char *, 8);
		NFSBCOPY(cp, cksum, 8);
		*toklenp = toklen;
		return (0);
	}
	return (AUTH_BADCRED);
nfsmout:
	return (error);
}

/*
 * GSS_Unwrap an area in an mbuf list starting at nd_md and nd_dpos, for
 * len bytes.
 * If successful, leave nd_md and nd_dpos pointing to the start of the
 * actual unencrypted data.
 * direction == 0 --> from initiator
 * direction == 1 --> from acceptor
 */
APPLESTATIC int
nfsgss_unwrap(struct nfsrv_descript *nd, int len, int direction,
    u_char *key, KEYSCHED_T sched)
{
	int mech, error;
	u_char seqnum[8], cksum[8], calcsum[8], *cp;

	/*
	 * Check the token and get the mech, cksum and seqnum.
	 */
DEBUG2PRINTF("in unwrap len=%d\n", len);
	error = nfsgss_cktoken(nd,&len,RPCAUTHGSS_WRAP,&mech,seqnum,cksum);
DEBUG2PRINTF("unwrap cktoken err=%d\n",error);
	if (error)
		return (error);

	/*
	 * De-encrypt the data and then calculate the checksum on
	 * the padded plain text with the 8 byte token header string
	 * prepended to it.
	 */
	switch (mech) {
	case KERBV_MECH:
		if (!(nd->nd_flag & ND_KERBV))
			return (NFSERR_GARBAGE);
		nd->nd_startm = nd->nd_md;
		nd->nd_startpos = nd->nd_dpos;
		/*
		 * Ok, now we decrypt the data.
		 */
DEBUG2PRINTF("unwrap at des len=%d\n",len);
		error = nfsgss_des(nd->nd_startm, nd->nd_startpos, len,
			key, 0);
DEBUG2PRINTF("unwrap aft des err=%d\n",error);
		if (error)
			return (error);
DEBUG2PRINTF("unwrap at desmac len=%d\n",len);
		nfsgss_desmacmd5(nd, len, sched, RPCAUTHGSS_WRAP, calcsum);
DEBUG2PRINTF("unwrap aft desmac 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n", cksum[0],cksum[1],cksum[2],cksum[3],cksum[4],cksum[5],cksum[6],cksum[7],calcsum[0],calcsum[1],calcsum[2],calcsum[3],calcsum[4],calcsum[5],calcsum[6],calcsum[7]);
		if (NFSBCMP(calcsum, cksum, 8))
			return (AUTH_PROBCRED);
		/*
		 * Just check the direction indicator, since RPCSEC_GSS
		 * does separate reply checking.
		 */
		CBCMODE((const KEY_T *)seqnum, (KEY_T *)seqnum, 8,
		    sched, cksum, 0);
		if ((direction == 0 &&
		     (seqnum[4] != 0x0 || seqnum[5] != 0x0 ||
		      seqnum[6] != 0x0 || seqnum[7] != 0x0)) ||
		    (direction != 0 &&
		     (seqnum[4] != 0xff || seqnum[5] != 0xff ||
		      seqnum[6] != 0xff || seqnum[7] != 0xff)))
			return (AUTH_PROBCRED);

		/*
		 * and throw away the confounder
		 */
		NFSM_DISSECT(cp, u_char *, 8);
		break;
	default:
		return (AUTH_PROBCRED);
	};
nfsmout:
DEBUG2PRINTF("eo unwrap err=%d\n",error);
	return (error);
}

/*
 * Perform des (de)encryption on len bytes starting at nd_startm, nd_startpos.
 * Returns EBADRPC if it runs off the end of the mbuf list, otherwise 0.
 */
APPLESTATIC int
nfsgss_des(mbuf_t startm, u_char *startpos, int len, u_char *key,
    int encrypt)
{
	mbuf_t m, m2;
	u_char *cp, *cp1, *cp2;
	int left, i, xfer, rem;
	u_char tmp[8], ivec[8];
	KEYSCHED_T sched;

	/*
	 * First, make the key schedule and initialize vars.
	 */
	for (i = 0; i < 8; i++) {
		tmp[i] = key[i] ^ 0xf0;
		ivec[i] = 0x0;
	}
	KEYSCHED(tmp, sched);
	m = startm;
	cp = startpos;
	left = NFSMTOD(m, u_char *) + mbuf_len(m) - cp;

	/*
	 * Now, loop through mbufs for len.
	 */
	while (len > 0) {
		if (left >= 8) {
			xfer = left - (left % 8);
			xfer = (xfer > len) ? len : xfer;
			CBCMODE_MULTIPLE((const KEY_T *)cp,
			    (KEY_T *)cp, xfer, sched, ivec, ivec, encrypt);
			cp += xfer;
			left -= xfer;
			len -= xfer;
		} else if (left > 0) {
			/*
			 * The fun case. Since des needs 8 bytes, copy
			 * 8 bytes into tmp, (de)encrypt and then copy
			 * it back.
			 */
			cp1 = tmp;
			cp2 = cp;
			for (i = 0; i < left; i++)
				*cp1++ = *cp2++;
			rem = 8 - left;
			m2 = m;
			while (rem > 0) {
				m2 = mbuf_next(m2);
				if (m2 == NULL)
					return (EBADRPC);
				xfer = (mbuf_len(m2)>rem) ? rem : mbuf_len(m2);
				if (xfer > 0) {
					cp2 = NFSMTOD(m2, caddr_t);
					for (i = 0; i < xfer; i++)
						*cp1++ = *cp2++;
					rem -= xfer;
				}
			}
			CBCMODE_MULTIPLE((const KEY_T *)tmp,
			   (KEY_T *)tmp, 8, sched, ivec, ivec, encrypt);
			/*
			 * Now we copy the 8 bytes back.
			 */
			cp1 = tmp;
			for (i = 0; i < left; i++)
				*cp++ = *cp1++;
			rem = 8 - left;
			while (rem > 0) {
				m = mbuf_next(m);
				if (m == NULL)
					panic("nfsgss des");
				if (mbuf_len(m) < rem) {
					xfer = mbuf_len(m);
					rem -= xfer;
				} else {
					xfer = rem;
					rem = 0;
					left = mbuf_len(m) - xfer;
					cp = NFSMTOD(m, caddr_t) + xfer;
				}
				if (xfer > 0) {
					cp2 = NFSMTOD(m, caddr_t);
					for (i = 0; i < xfer; i++)
						*cp2++ = *cp1++;
				}
			}
			len -= 8;
		} else {
			m = mbuf_next(m);
			if (m == NULL)
				return (EBADRPC);
			cp = NFSMTOD(m, caddr_t);
			left = mbuf_len(m);
		}
	}
	return (0);
}

/*
 * Calculate an DES-MAC-MD5 checksum on the data from
 * (nd_startmd, nd_startdpos) for len bytes.
 */
APPLESTATIC void
nfsgss_desmacmd5(struct nfsrv_descript *nd, int len, KEYSCHED_T sched,
    int mechval, u_char *calcsum)
{
	mbuf_t m;
	caddr_t cp;
	int left, i;
	MD5_CTX mdcont;
	u_char digest[16];

	MD5Init(&mdcont);
	if (!(nd->nd_flag & ND_KERBV))
		panic("desmac mech");
	if (mechval == RPCAUTHGSS_MIC)
		MD5Update(&mdcont, kerbv_micstr, 8);
	else
		MD5Update(&mdcont, kerbv_wrapstr, 8);
	m = nd->nd_startm;
	cp = nd->nd_startpos;
	left = NFSMTOD(m, caddr_t) + mbuf_len(m) - cp;

	/*
	 * Now, loop through mbufs for len.
	 */
	while (len > 0) {
		i = (len > left) ? left : len;
		if (i > 0) {
			MD5Update(&mdcont, cp, i);
			len -= i;
		}
		if (len > 0) {
			m = mbuf_next(m);
			if (m == NULL)
				panic("nfsgss");
			cp = NFSMTOD(m, caddr_t);
			left = mbuf_len(m);
		}
	}
	MD5Final(digest, &mdcont);

	/*
	 * Now, get the DES MAC for the digest.
	 */
	MACCKSUM(digest, calcsum, 16, sched, ivec0);
}

/*
 * Find the mechanism by searching for the bytes in the list.
 */
static int
nfsgss_findmech(u_char *cp, int len)
{
	struct nfsgss_mechlist *mechp = nfsgss_mechlist;
	int i = 0;

	while (mechp->len > 0) {
		if (mechp->len == len && !NFSBCMP(cp, mechp->str, len))
			return (i);
		mechp++;
		i++;
	}
	return (-1);
}

/*
 * Create or check a RPCGSS verifier.
 */
APPLESTATIC int
nfsgss_verf(struct nfsrv_descript *nd, KEYSCHED_T sched, u_int32_t seqnum,
    struct nfsreq *rep, int toklen, u_char *retsum)
{
	u_char retseqnum[8], cksum[8], calcsum[8];
	int error, mech, totlen, len;

	if (nd->nd_flag & ND_KERBV) {
		nfsgss_calcverf((nd->nd_flag & ND_GSS), sched, seqnum,
		    calcsum);
		if (rep == NULL) {
			/*
			 * This is call done in the server to calculate
			 * the verifier for a reply.
			 */
			len = nfsgss_createtok(nd, sched, 0, calcsum,
				0, RPCAUTHGSS_MIC);
DEBUG2PRINTF("in verf aft createtok len=%d\n",len);
		} else {
			/*
			 * This call is being done for client or callback.
			 * Check the token and get the mech, cksum and seqnum.
			 */
			totlen = NFSM_RNDUP(toklen);
			error = nfsgss_cktoken(nd, &totlen, RPCAUTHGSS_MIC,
				&mech, retseqnum, cksum);
DEBUG2PRINTF("aft verf tok err=%d totlen=%d\n",error,totlen);
			if (error == 0 && totlen > 0)
				error = nfsm_advance(nd, totlen, -1);
			if (error)
				return (error);
			/*
			 * For the call in nfsgss_getclhandle() doing the
			 * Null RPC with GSS Initialization stuff, return
			 * the sum here.
			 */
			if (retsum != NULL) {
				NFSBCOPY(cksum, retsum, 8);
				return (0);
			}

			/*
			 * If there were retries, the reply could be for
			 * This case is checking a verifier in a received
			 * reply.
			 * The reply could be for any of the seq#
			 * used in the request (original or retries), so
			 * test for each.
			 */
			if (NFSBCMP(calcsum, cksum, 8)) {
			    while (nfsgss_nextclseqnum(rep, &seqnum)) {
				nfsgss_calcverf((nd->nd_flag & ND_GSS), sched,
				    seqnum, calcsum);
				if (!NFSBCMP(calcsum, cksum, 8)) {
					/*
					 * Save this seqnum as the correct
					 * one for use for Integrity or
					 * Privacy.
					 */
					rep->r_seqnum = seqnum;
					return (0);
				}
			    }
			    nfsgsscl_seqmisscnt++;
			    return (AUTH_PROBCRED);
			}
		}
	} else {
		printf("gss verf when not ND_KERBV\n");
		return (EAUTH);
	}
	return (0);
}

/*
 * Calculate the value of the verifier.
 */
static void
nfsgss_calcverf(int mechflag, KEYSCHED_T sched, u_int32_t seqnum,
    u_char *calcsum)
{
	u_int32_t ckval;
	MD5_CTX mdcont;
	u_char digest[16];

	ckval = htonl(seqnum);
	if (mechflag & ND_KERBV) {
		MD5Init(&mdcont);
		MD5Update(&mdcont, kerbv_micstr, 8);
		MD5Update(&mdcont, (char *)&ckval, 4);
		MD5Final(digest, &mdcont);
		MACCKSUM(digest, calcsum, 16, sched, ivec0);
	}
}

/*
 * Create a GSS token header.
 * Return the length of the token in bytes.
 */
APPLESTATIC int
nfsgss_createtok(struct nfsrv_descript *nd, KEYSCHED_T sched, int initiator,
    u_char *calcsum, int datalen, int toktype)
{
	u_char *cp;
	int i;
	struct nfsgss_mechlist *mechp = NULL;
	u_int32_t *tl;
	u_char *cpsav;
	union {
		u_int32_t	lval;
		u_char		cval[4];
	} u_l;
	int toklen = 0, len, padsiz;
	static u_int32_t seqnum = 0;

	if (nd->nd_flag & ND_KERBV) {
		mechp = &nfsgss_mechlist[KERBV_MECH];
		toklen = mechp->totlen + GSSX_KERBVTOKEN;
	} else
		panic("nfsgss createtok");
	len = toklen + datalen;
DEBUG2PRINTF("createtok toklen=%d len=%d toktype=%d\n",toklen,len,toktype);
	NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
	NFSM_BUILD(cp, u_char *, 1);
	*cp = 0x60;
	len += nfsgss_putlen(nd, len);
	NFSM_BUILD(cp, u_char *, 2 + mechp->len);
	*cp++ = 0x6;
	*cp++ = (u_char)(mechp->len);
	len++;
	NFSBCOPY(mechp->str, cp, mechp->len);
	*tl = txdr_unsigned(len);
DEBUG2PRINTF("createtok aft xdrlen=%d\n",len);
	if (nd->nd_flag & ND_KERBV) {
		NFSM_BUILD(cp, u_char *, 8);
		if (toktype == RPCAUTHGSS_MIC) {
			NFSBCOPY(kerbv_micstr, cp, 8);
		} else {
			NFSBCOPY(kerbv_wrapstr, cp, 8);
DEBUG2PRINTF("put wrap str\n");
		}
		NFSM_BUILD(cp, u_char *, 8);
		cpsav = cp;
		/*
		 * Now, fill in the KerbV sequence #.
		 * (Since RFC2203 doesn't allow GSS sequence checking,
		 *  I don't think this value should matter?)
		 * If it does, code must be added to get the correct
		 * initial values from the gssd and they need to be saved
		 * in nfsgss_handleref.
		 */
		u_l.lval = ++seqnum;
#if BYTE_ORDER == BIG_ENDIAN
		for (i = 3; i >= 0; i--)
#else
		for (i = 0; i < 4; i++)
#endif
			*cp++ = u_l.cval[i];
		for (i = 0; i < 4; i++) {
			if (initiator)
				*cp++ = 0x0;
			else
				*cp++ = 0xff;
		}
		NFSM_BUILD(cp, u_char *, 8);
		NFSBCOPY(calcsum, cp, 8);
DEBUG2PRINTF("copied calcsum\n");
		CBCMODE((const KEY_T *)cpsav, (KEY_T *)cpsav, 8, sched, cp, 1);
	}

	/*
	 * For GSS_GetMIC() tokens, xdr pad it to a multiple of 4 bytes,
	 * since these tokens are always in an xdr'd opaque field. For
	 * GSS_Wrap(), the data follows right after the token and the
	 * padding must be appended to the data. (Length is returned so
	 * the caller can do this.)
	 */
	if (toktype == RPCAUTHGSS_MIC) {
		padsiz = NFSM_RNDUP(len) - len;
		if (padsiz > 0) {
			NFSM_BUILD(cp, u_char *, padsiz);
			for (i = 0; i < padsiz; i++)
				*cp++ = 0x0;
		}
	}
DEBUG2PRINTF("createtok retlen=%d\n",len);
	return (len);
}

/*
 * Calculate the length of a GSS token and return it.
 */
APPLESTATIC int
nfsgss_lengthtok(struct nfsrv_descript *nd, int datalen)
{
	int toklen = 0, len;
	struct nfsgss_mechlist *mechp;

	if (nd->nd_flag & ND_KERBV) {
		mechp = &nfsgss_mechlist[KERBV_MECH];
		toklen = mechp->totlen + GSSX_KERBVTOKEN;
	} else
		panic("nfsgss createtok");
	len = toklen + datalen;
	len += nfsgss_calcputlen(len);
	len++;
	return (len);
}

/*
 * This function checks a sequence number for being within the window.
 * (Returns 1 if the check fails and 0 for success.)
 */
APPLESTATIC int
nfsgss_ckseqnum(struct nfsrv_descript *nd, struct nfsgss_handle *gssp)
{
	u_int32_t i;
	u_int64_t newbit;

	if (!(gssp->nfsg_flag & NFSG_SETWIN)) {
		/*
		 * First use for this context.
		 */
		if (nd->nd_seqnum >= RPCAUTHGSS_MAXSEQ)
			return (1);
		gssp->nfsg_curwin = nd->nd_seqnum;
		gssp->nfsg_window = 0;
		gssp->nfsg_flag |= NFSG_SETWIN;
		return (0);
	}

	if (nd->nd_seqnum > gssp->nfsg_curwin) {
		i = nd->nd_seqnum - gssp->nfsg_curwin;
		/*
		 * Move window along, by shifting the bits.
		 */
		if (i >= RPCAUTHGSS_WINDOW)
			gssp->nfsg_window = 0;
		else
			gssp->nfsg_window <<= i;
		/*
		 * If previous seq# is still within window, set the bit for it
		 * and advance the window to this seq#.
		 */
		if (i <= RPCAUTHGSS_WINDOW) {
			newbit = 0x1;
			newbit <<= (i - 1);
			gssp->nfsg_window |= newbit;
		}
		gssp->nfsg_curwin = nd->nd_seqnum;
		return (0);
	}

	i = gssp->nfsg_curwin - nd->nd_seqnum;
	if (i == 0 || i > RPCAUTHGSS_WINDOW)
		return (1);
	/*
	 * Within window, so check the bit.
	 */
	newbit = 0x1;
	newbit <<= (i - 1);
	if (gssp->nfsg_window & newbit)
		return (1);
	/*
	 * Ok, so set bit.
	 */
	gssp->nfsg_window |= newbit;
	return (0);
}

/*
 * Calculate the wrapper checksum.
 */
APPLESTATIC void
nfsgss_wrapcksum(struct nfsrv_descript *nd, int len, u_char *calcsum)
{

	if (nd->nd_flag & ND_KERBV) {
		nfsgss_desmacmd5(nd, len, nd->nd_gssp->nfsg_sched,
			RPCAUTHGSS_WRAP, calcsum);
	} else
		panic("nfswrapck");
}

/*
 * Fill in a length field. Could this be any simpler.
 */
static int
nfsgss_putlen(struct nfsrv_descript *nd, int len)
{
	u_char *cp;
	int siz;

	if (len < 128) {
		NFSM_BUILD(cp, u_char *, 1);
		*cp = (u_char)(len & 0xff);
		siz = 1;
	} else if (len < 256) {
		NFSM_BUILD(cp, u_char *, 2);
		*cp++ = 0x81;
		*cp = (u_char)(len & 0xff);
		siz = 2;
	} else if (len < 65536) {
		NFSM_BUILD(cp, u_char *, 3);
		*cp++ = 0x82;
		*cp++ = (u_char)((len >> 8) & 0xff);
		*cp = (u_char)(len & 0xff);
		siz = 3;
	} else if (len < 16777216) {
		NFSM_BUILD(cp, u_char *, 4);
		*cp++ = 0x83;
		*cp++ = (u_char)((len >> 16) & 0xff);
		*cp++ = (u_char)((len >> 8) & 0xff);
		*cp = (u_char)(len & 0xff);
		siz = 4;
	} else {
		NFSM_BUILD(cp, u_char *, 5);
		*cp++ = 0x84;
		*cp++ = (u_char)((len >> 24) & 0xff);
		*cp++ = (u_char)((len >> 16) & 0xff);
		*cp++ = (u_char)((len >> 8) & 0xff);
		*cp = (u_char)(len & 0xff);
		siz = 5;
	}
	return (siz);
}

/*
 * Return the number of bytes required to nfsgss_putlen().
 */
static int
nfsgss_calcputlen(int len)
{

	if (len < 128)
		return (1);
	else if (len < 256)
		return (2);
	else if (len < 65536)
		return (3);
	else if (len < 16777216)
		return (4);
	else
		return (5);
}

/*
 * Initialize the gss context cache list
 */
APPLESTATIC void
nfsgss_initcache(void)
{
	int i;

	for (i = 0; i < NFSGSSCACHE_HASHSIZE; i++)
		LIST_INIT(&nfsgsshashtbl[i]);
	TAILQ_INIT(&nfsgsslruhead);
	LIST_INIT(&nfsgssclsockhead);
	newnfsstats.gsscache_size = 0;
	nfsgss_cachesize = 0;
}

/*
 * Look for the handle in the cache
 * If hp is NULL, add a new one, otherwise return an error if not found.
 */
APPLESTATIC int
nfsgss_gethandle(u_char *hp, struct nfsgss_handle **gsspp, int continit,
    NFSPROC_T *p)
{
	struct nfsgss_handle *gssp;

	NFSLOCKGSSHANDLE;
	if (hp) {
tryagain:
	    LIST_FOREACH(gssp, NFSGSSHASH(hp, GSSX_MYHANDLE), nfsg_hash) {
		if (!NFSBCMP(gssp->nfsg_handle, hp, GSSX_MYHANDLE)) {
			if (gssp->nfsg_flag & NFSG_LOCKED) {
				gssp->nfsg_flag |= NFSG_WANTED;
				(void) nfsmsleep((caddr_t)gssp,
				    NFSGSSHANDLEMUTEXPTR, PZERO - 1, "nfsgss",
				    NULL);
				goto tryagain;
			}
			if ((gssp->nfsg_flag & NFSG_DESTROYED) ||
			    (continit && !(gssp->nfsg_flag & NFSG_CONTINIT)) ||
			    (!continit && !(gssp->nfsg_flag & NFSG_COMPLETE))){
				NFSUNLOCKGSSHANDLE;
				if (continit)
					return (AUTH_BADCRED);
				else
					return (AUTH_PROBCRED);
			}
			if (continit)
				gssp->nfsg_flag |= NFSG_LOCKED;
			gssp->nfsg_ref++;
			TAILQ_REMOVE(&nfsgsslruhead, gssp, nfsg_lru);
			TAILQ_INSERT_TAIL(&nfsgsslruhead, gssp, nfsg_lru);
			*gsspp = gssp;
			nfsgss_trimcache(p);
			NFSUNLOCKGSSHANDLE;
			return (0);
		}
	    }
	}
	nfsgss_trimcache(p);
	NFSUNLOCKGSSHANDLE;
	if (continit)
		return (AUTH_BADCRED);
	else
		return (AUTH_PROBCRED);
}

/*
 * Release a context entry. When nfsg_ref == 0, it is not currently
 * in use by the code, although the context is still valid.
 */
APPLESTATIC void
nfsgss_releasehandle(struct nfsgss_handle *gssp, NFSPROC_T *p)
{

	NFSLOCKGSSHANDLE;
	if (gssp->nfsg_ref == 0)
		panic("nfsgss ref neg");
	gssp->nfsg_ref--;
	gssp->nfsg_flag &= ~NFSG_LOCKED;
	if (gssp->nfsg_flag & NFSG_WANTED) {
		gssp->nfsg_flag &= ~NFSG_WANTED;
		wakeup((caddr_t)gssp);
	}
	if ((gssp->nfsg_flag & NFSG_DESTROYED) && gssp->nfsg_ref == 0)
		nfsgss_destroyhandle(gssp, p);
	nfsgss_trimcache(p);
	NFSUNLOCKGSSHANDLE;
}

/*
 * Mark the handle destroyed. It will be tossed when released.
 */
APPLESTATIC void
nfsgss_destroy(struct nfsgss_handle *gssp)
{

	NFSLOCKGSSHANDLE;
	gssp->nfsg_flag |= NFSG_DESTROYED;
	NFSUNLOCKGSSHANDLE;
}

/*
 * Destroy a context.
 */
static void
nfsgss_destroyhandle(struct nfsgss_handle *gssp, NFSPROC_T *p)
{

	NFSGSSHANDLEREQUIRED;
	LIST_REMOVE(gssp, nfsg_hash);
	TAILQ_REMOVE(&nfsgsslruhead, gssp, nfsg_lru);
	if (gssp->nfsg_flag & NFSG_WANTED)
		wakeup((caddr_t)gssp);
	if (gssp->nfsg_flag & NFSG_CONTINIT)
		(void) nfsgss_continitdestroy(gssp, p);
	FREE((caddr_t)gssp, M_NFSGSS);
	nfsgss_cachesize--;
	newnfsstats.gsscache_size--;
}

/*
 * Trim expired entries out of the cache.
 */
static void
nfsgss_trimcache(NFSPROC_T *p)
{
	struct nfsgss_handle *gssp, *ngssp;

	NFSGSSHANDLEREQUIRED;
	gssp = TAILQ_FIRST(&nfsgsslruhead);
	while (gssp != TAILQ_END(&nfsgsslruhead)) {
		ngssp = TAILQ_NEXT(gssp, nfsg_lru);
		if (gssp->nfsg_ref == 0 &&
		    !(gssp->nfsg_flag & (NFSG_LOCKED | NFSG_WANTED)) &&
		    (gssp->nfsg_expire < NFSD_MONOSEC ||
		     nfsgss_cachesize > nfsgss_highwater))
			nfsgss_destroyhandle(gssp, p);
		gssp = ngssp;
	}
}

/*
 * Clean out the cache.
 */
#ifdef notnow
static void
nfsgss_cleancache(NFSPROC_T *p)
{
	struct nfsgss_handle *gssp, *ngssp;

	NFSLOCKGSSHANDLE;
	gssp = TAILQ_FIRST(&nfsgsslruhead);
	while (gssp != TAILQ_END(&nfsgsslruhead)) {
		ngssp = TAILQ_NEXT(gssp, nfsg_lru);
		nfsgss_destroyhandle(gssp, p);
		gssp = ngssp;
	}
	newnfsstats.gsscache_size = 0;
	nfsgss_cachesize = 0;
	NFSUNLOCKGSSHANDLE;
}
#endif

/*
 * Clean out any handles that aren't complete.
 */
static void
nfsgss_cleanincomplete(NFSPROC_T *p)
{
	struct nfsgss_handle *gssp, *ngssp;

	NFSLOCKGSSHANDLE;
	gssp = TAILQ_FIRST(&nfsgsslruhead);
	while (gssp != TAILQ_END(&nfsgsslruhead)) {
		ngssp = TAILQ_NEXT(gssp, nfsg_lru);
		if (!(gssp->nfsg_flag & NFSG_COMPLETE))
			nfsgss_destroyhandle(gssp, p);
		gssp = ngssp;
	}
	NFSUNLOCKGSSHANDLE;
}

/*
 * Generate a new GSS handle for the server.
 */
static void
nfsgss_newgsshandle(struct nfsgss_handle *gssp)
{
	static u_int64_t next_seq = 0;

	NFSBCOPY((caddr_t)&nfsrvboottime, gssp->nfsg_handle,
	    sizeof (long));
	next_seq++;
	NFSBCOPY((caddr_t)&next_seq, &gssp->nfsg_handle[sizeof (long)],
	    sizeof (u_int64_t));
}

/*
 * Calculate the hash code for the GSS handle.
 */
static int
nfsgss_hash(u_char *cp, int len)
{
	int i, hash = 0;

	for (i = 0; i < len; i++)
		hash += *cp++;
	return (hash);
}

/*
 * Called to Initialize or Continue Initialization of a Context.
 * (Basically, just pass it up to the userland gssd and wait for
 *  the reply.)
 */
#define	NFSGSS_GUESSNAMELEN	100
APPLESTATIC int
nfsgss_continit(struct nfsrv_descript *realnd, int gssproc,
    struct nfsgss_handle **gsspp, NFSPROC_T *p)
{
	u_int32_t *tl;
	struct nfsrv_descript *nd;
	u_char *cp;
	int i, j;
	struct nfsgss_handle *gssp = *gsspp;
	struct nfsgss_handle *ogssp;
	struct nfsrv_descript nfsd;
	struct ucred *cred;
	struct sockaddr_in *saddr;
	int sock = 0, error, len;
	u_int32_t major, minor, time_rec;
	static u_int next_sock = 0;

	/*
	 * Since the NFSM_xxx macros use "nd", it must be overloaded as
	 * the pointer to both realnd and the local nfsd at times.
	 * It points to the local nfsd for now.
	 */
	nd = &nfsd;
	nd->nd_mrep = NULL;
	cred = newnfs_getcred();
	if (nfsgss_numgssd > 0) {
	    nd->nd_flag = ND_GSSINITREPLY;

	    /*
	     * The token is in realnd->nd_mrep at <nd_md, nd_dpos>, so
	     * trim the leading mbuf(s) off and hang it on the request.
	     */
	    newnfs_trimleading(realnd);
	    if (gssproc == RPCAUTHGSS_INIT) {
		next_sock++;
		next_sock %= nfsgss_numgssd;
		sock = next_sock;
		nd->nd_procnum = RPCGSSD_INIT;
		nfsrvd_rephead(nd);
		/*
		 * XXX someday this needs to know how to do AF_INET6.
		 */
		saddr = NFSSOCKADDR(realnd->nd_nam, struct sockaddr_in *);
		NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
		*tl++ = txdr_unsigned(AF_INET);
		*tl = txdr_unsigned(saddr->sin_addr.s_addr);
		mbuf_setnext(nd->nd_mb, realnd->nd_md);
	    } else {
		sock = gssp->nfsg_sock;
		if (sock >= nfsgss_numgssd)
			goto nfsmout;
		nd->nd_procnum = RPCGSSD_CONTINIT;
		nfsrvd_rephead(nd);
		/*
		 * XXX someday this needs to know how to do AF_INET6.
		 */
		saddr = NFSSOCKADDR(realnd->nd_nam, struct sockaddr_in *);
		NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
		*tl++ = txdr_unsigned(AF_INET);
		*tl = txdr_unsigned(saddr->sin_addr.s_addr);
		NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
		*tl = txdr_unsigned(GSSX_LOCALHANDLE);
		i = NFSM_RNDUP(GSSX_LOCALHANDLE);
		NFSM_BUILD(cp, u_char *, i);
		NFSBCOPY(gssp->nfsg_localhandle, cp,
		    GSSX_LOCALHANDLE);
		i -= GSSX_LOCALHANDLE;
		if (i > 0) {
			cp += GSSX_LOCALHANDLE;
			for (j = 0; j < i; j++)
				*cp++ = 0x0;
		}
		mbuf_setnext(nd->nd_mb, realnd->nd_md);
	    }
DEBUG2PRINTF("at gssd upcall proc=%d\n",nd->nd_procnum);
	    error = newnfs_request(nd, NULL, NULL, &nfsgss_sock[sock], NULL,
		NULL, cred, RPCPROG_GSSD, RPCGSSD_VERS, NULL, 0, NULL);
DEBUG2PRINTF("aft gssd upcall err=%d stat=%d\n",error,nd->nd_repstat);

	    /*
	     * Since the first mbufs in realnd->nd_mrep were free'd by
	     * newnfs_trimleading() and the rest were free'd by
	     * newnfs_request() as a part of nfsd.nd_mreq, set it
	     * NULL, so it won't be free'd again later.
	     */
	    realnd->nd_mrep = NULL;
	    if (error) {
		nd->nd_mrep = NULL;
		major = GSS_S_UNAVAILABLE;
		minor = 0;
	    } else {
		major = nd->nd_repstat;
		NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
		minor = fxdr_unsigned(u_int32_t, *tl);
	    }
	} else {
	    /*
	     * Get rid of the request now, to be consistent.
	     * (This shouldn't be necessary, since it would be free'd
	     *  later if not set to NULL.)
	     */
	    mbuf_freem(realnd->nd_mrep);
	    realnd->nd_mrep = NULL;
nfsmout:
	    major = GSS_S_UNAVAILABLE;
	    minor = 0;
	}

	ogssp = NULL;
	if (major == GSS_S_COMPLETE) {
		NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
		len = fxdr_unsigned(int, *tl);
		if (gssp && len > NFSGSS_GUESSNAMELEN) {
			ogssp = gssp;
			gssp = NULL;
		}
	} else {
		len = NFSGSS_GUESSNAMELEN; /* just a guess */
	}
	if (gssp == NULL && (major == GSS_S_COMPLETE ||
		major == GSS_S_CONTINUE_NEEDED)) {
		MALLOC(gssp, struct nfsgss_handle *,
			sizeof (struct nfsgss_handle) + len, M_NFSGSS,
			M_WAITOK);
		NFSBZERO((caddr_t)gssp, sizeof (struct nfsgss_handle));
		gssp->nfsg_ref = 1;
		gssp->nfsg_flag = (NFSG_LOCKED | NFSG_CONTINIT);
		gssp->nfsg_expire = NFSD_MONOSEC + 60;
		if (ogssp) {
			if (ogssp->nfsg_ref != 1)
				panic("nfsgss refnot1");
			NFSBCOPY(ogssp->nfsg_handle, gssp->nfsg_handle,
				GSSX_MYHANDLE);
		} else {
			nfsgss_newgsshandle(gssp);
		}
		NFSLOCKGSSHANDLE;
		TAILQ_INSERT_TAIL(&nfsgsslruhead, gssp, nfsg_lru);
		LIST_INSERT_HEAD(NFSGSSHASH(gssp->nfsg_handle,
		    GSSX_MYHANDLE), gssp, nfsg_hash);
		*gsspp = gssp;
		nfsgss_cachesize++;
		newnfsstats.gsscache_size++;
		if (ogssp) {
			ogssp->nfsg_flag &= ~NFSG_CONTINIT;
			nfsgss_destroyhandle(ogssp, p);
		}
		NFSUNLOCKGSSHANDLE;
	}

	/*
	 * Now, parse out the rest of the gssd reply in nfsd.nd_mrep for
	 * successful replies.
	 */
	if (major == GSS_S_CONTINUE_NEEDED) {
		gssp->nfsg_sock = sock;
		NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
		len = fxdr_unsigned(int, *tl);
DEBUG2PRINTF("cont needed handlelen=%d\n",len);
		if (len != GSSX_LOCALHANDLE)
			goto nfsmout;
		NFSM_DISSECT(cp, u_char *, NFSM_RNDUP(GSSX_LOCALHANDLE));
		NFSBCOPY(cp, gssp->nfsg_localhandle, GSSX_LOCALHANDLE);
	} else if (major == GSS_S_COMPLETE) {
DEBUG2PRINTF("complete reply\n");
		gssp->nfsg_namelen = len;
		if (len > 0) {
		    error = nfsrv_mtostr(nd, gssp->nfsg_name, len);
		    if (error)
			goto nfsmout;
		}
		NFSM_DISSECT(tl, u_int32_t *, 4 * NFSX_UNSIGNED);
		if (fxdr_unsigned(int, *tl++))
		    gssp->nfsg_flag |= NFSG_NAME;
		gssp->nfsg_seqnum = fxdr_unsigned(u_int32_t, *tl++);
		gssp->nfsg_cr.nfsc_uid = fxdr_unsigned(uid_t, *tl++);
		len = fxdr_unsigned(int, *tl);
		NFSM_DISSECT(tl, u_int32_t *, (len + 1) * NFSX_UNSIGNED);

		/* nfsc_groups[] is of size NGROUPS + 1. */
		for (i = 0; i < len; i++)
		    if (i <= NGROUPS)
			gssp->nfsg_cr.nfsc_groups[i] =
			    fxdr_unsigned(gid_t, *tl++);
		    else
			tl++;
		gssp->nfsg_cr.nfsc_ngroups = (len > (NGROUPS + 1)) ?
		    (NGROUPS + 1) : len;
		len = fxdr_unsigned(int, *tl);
		/*
		 * XXX For now, must be RPCGSS_KEYSIZ in size (a DES key).
		 */
		if (len != RPCGSS_KEYSIZ)
			panic("nfsgss keysize");
		NFSM_DISSECT(cp, u_char *, len);
		NFSBCOPY(cp, gssp->nfsg_key, len);
		NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
		time_rec = fxdr_unsigned(u_int32_t, *tl);
DEBUG2PRINTF("continit keysched\n");
		KEYSCHED(gssp->nfsg_key, gssp->nfsg_sched);
DEBUG2PRINTF("aft keysched\n");
		gssp->nfsg_expire = NFSD_MONOSEC + time_rec;
		gssp->nfsg_flag &= ~NFSG_CONTINIT;
		gssp->nfsg_flag |= NFSG_COMPLETE;
DEBUG2PRINTF("eo complete\n");
	}

	/*
	 * Now, create the reply.
	 * (Set nd to realnd for NFSM_BUILD.)
	 */
	nd = realnd;
	nd->nd_flag |= ND_GSSINITREPLY;
	nfsrvd_rephead(nd);
	if (major == GSS_S_COMPLETE || major == GSS_S_CONTINUE_NEEDED) {
		/*
		 * Set the flags to reply with the correct verifier.
		 * XXX KerberosV only for now.
		 */
		if (major == GSS_S_CONTINUE_NEEDED) {
		    nd->nd_flag &= ~ND_GSS;
		} else {
		    nd->nd_flag |= (ND_WINDOWVERF | ND_KERBV);
		    nd->nd_seqnum = RPCAUTHGSS_SEQWINDOW;
		}

		/*
		 * Similar to the above, the token is now in nfsd.nd_md,
		 * so trim off the leading mbuf(s) and then hang it on
		 * the reply. (As such, nfsd.nd_mrep shouldn't be free'd.
		 */
		newnfs_trimleading(&nfsd);
		NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
		*tl = txdr_unsigned(GSSX_MYHANDLE);
		NFSM_BUILD(cp, u_char *, GSSX_MYHANDLE);
		NFSBCOPY(gssp->nfsg_handle, cp, GSSX_MYHANDLE);
		NFSM_BUILD(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
		*tl++ = txdr_unsigned(major);
		*tl++ = txdr_unsigned(minor);
		*tl = txdr_unsigned(RPCAUTHGSS_SEQWINDOW);
		mbuf_setnext(nd->nd_mb, nfsd.nd_md);	/* and the token */
DEBUG2PRINTF("complete reply major=0x%x minor=%d\n",major,minor);
	} else {
		nd->nd_flag &= ~ND_GSS; /* So a null verifier is replied */
		NFSM_BUILD(tl, u_int32_t *, 5 * NFSX_UNSIGNED);
		*tl++ = 0;	/* Null handle */
		*tl++ = txdr_unsigned(major);
		*tl++ = txdr_unsigned(minor);
		*tl++ = 0;	/* seq window */
		*tl = 0;	/* Null token */
i = minor; DEBUG2PRINTF("continit fail reply major=0x%x minor=%d\n",major,i);
		/*
		 * If newnfs_request() succeeded, but the result was not
		 * successful, there will be an nfsd.nd_mrep that needs
		 * to be free'd.
		 */
		if (nfsd.nd_mrep)
			mbuf_freem(nfsd.nd_mrep);
	}
	NFSFREECRED(cred);
	if (gssp && major != GSS_S_COMPLETE && major != GSS_S_CONTINUE_NEEDED)
		nfsgss_destroy(gssp);
	return (0);
}

/*
 * Do an upcall when a handle in CONTINIT state is being destroyed, so
 * the appropriate gssd can free up the associated context in the gssd.
 * (If this isn't done, there would be a potential memory leak in the
 *  gssd.)
 */
static int
nfsgss_continitdestroy(struct nfsgss_handle *gssp, NFSPROC_T *p)
{
	u_int32_t *tl;
	struct nfsrv_descript *nd;
	u_char *cp;
	int i, j;
	struct nfsrv_descript nfsd;
	struct ucred *cred;
	int sock, error;

	nd = &nfsd;
	cred = newnfs_getcred();
	nd->nd_flag = ND_GSSINITREPLY;

	sock = gssp->nfsg_sock;
	if (sock >= nfsgss_numgssd)
		return (EPERM);
	nd->nd_procnum = RPCGSSD_CONTINITDESTROY;
	nfsrvd_rephead(nd);
	NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
	*tl = txdr_unsigned(GSSX_LOCALHANDLE);
	i = NFSM_RNDUP(GSSX_LOCALHANDLE);
	NFSM_BUILD(cp, u_char *, i);
	NFSBCOPY(gssp->nfsg_localhandle, cp,
		GSSX_LOCALHANDLE);
	i -= GSSX_LOCALHANDLE;
	if (i > 0) {
		cp += GSSX_LOCALHANDLE;
		for (j = 0; j < i; j++)
			*cp++ = 0x0;
	}
	error = newnfs_request(nd, NULL, NULL, &nfsgss_sock[sock], NULL, NULL,
	    cred, RPCPROG_GSSD, RPCGSSD_VERS, NULL, 0, NULL);
	NFSFREECRED(cred);
DEBUG2PRINTF("aft continitdestroy upcall err=%d stat=%d\n",error,nd->nd_repstat);
	if (!error) {
		mbuf_freem(nd->nd_mrep);
		error = nd->nd_repstat;
	}
	return (error);
}

/*
 * Add a socket to the gssd list.
 */
APPLESTATIC int
nfsgss_addport(u_short port, NFSPROC_T *p)
{
	struct nfssockreq *rp;
	struct sockaddr_in *ad;
	struct nfssockreq sq;
	int error, thisgss;

DEBUG2PRINTF("in addport numgssd=%d port=%d\n",nfsgss_numgssd,port);
	NFSLOCKGSSHANDLE;
	if (nfsgss_numgssd >= NFSGSS_MAXGSSD) {
		NFSUNLOCKGSSHANDLE;
		return (ENOENT);
	}
	thisgss = nfsgss_numgssd;
	nfsgss_numgssd++;
	NFSUNLOCKGSSHANDLE;
	/*
	 * Set up the socket record and connect.
	 */
	rp = &sq;
	rp->nr_so = NULL;
	rp->nr_sotype = SOCK_DGRAM;
	rp->nr_soproto = IPPROTO_UDP;
	rp->nr_lock = (NFSR_RESERVEDPORT | NFSR_LOCALHOST);
	NFSSOCKADDRALLOC(rp->nr_nam);
	NFSSOCKADDRSIZE(rp->nr_nam, sizeof (struct sockaddr_in));
	ad = NFSSOCKADDR(rp->nr_nam, struct sockaddr_in *);
	ad->sin_family = AF_INET;
	ad->sin_addr.s_addr = htonl((u_int32_t)0x7f000001);	/* 127.0.0.1 */
	ad->sin_port = port;
	error = newnfs_connect(NULL, rp, NULL, NFSPROCCRED(p), p);
DEBUG2PRINTF("addport aft conn err=%d\n",error);
	if (error) {
		NFSSOCKADDRFREE(rp->nr_nam);
		NFSLOCKGSSHANDLE;
		nfsgss_numgssd--;
		NFSUNLOCKGSSHANDLE;
		return (error);
	}

	/*
	 * Connected, so now add to the list.
	 */
	NFSBCOPY((caddr_t)rp, (caddr_t)&nfsgss_sock[thisgss],
	    sizeof (struct nfssockreq));
	return (0);
}

/*
 * Delete all ports.
 */
APPLESTATIC void
nfsgss_deleteallports(NFSPROC_T *p)
{
	int i, numgssd;

	NFSLOCKGSSHANDLE;
	numgssd = nfsgss_numgssd;
	nfsgss_numgssd = 0;
	NFSUNLOCKGSSHANDLE;
	if (numgssd == 0)
		return;
	for (i = 0; i < numgssd; i++) {
		newnfs_disconnect(&nfsgss_sock[i]);
		NFSSOCKADDRFREE(nfsgss_sock[i].nr_nam);
	}
	nfsgss_cleanincomplete(p);
}

#if !defined(APPLEKEXT) || APPLEKEXT != 1050
/*
 * Get the context handle for a client side GSS request.
 */
static int
nfsgss_getclhandle(struct nfsreq *rep, u_char *name, int namelen, uid_t uid,
    u_int32_t mechflag, int clientside)
{
	u_int32_t *tl;
	struct nfsrv_descript *nd;
	u_char *cp;
	int i;
	struct nfsgss_handleref *handp = rep->r_handp;
	struct nfsgss_handlebuf *handb = rep->r_handb;
	struct nfsrv_descript nfsd;
	struct ucred cr;
	struct sockaddr_in *sad;
	struct nfssockreq *sock = NULL;
	struct nfsgssclsock *gcp = NULL;
	int error, len = 0;
	u_int32_t major, srvmajor = GSS_S_CONTINUE_NEEDED, winsize = 0;
	u_char retsum[8], calcsum[8];
	static u_int next_sock = 0;

DEBUG1PRINTF("getcl nl=%d uid=%d cls=%d\n",namelen,uid,clientside);
	/*
	 * If this is a client side request for a uid and there is
	 * a gsscl running, use it. Otherwise, use a gssd.
	 */
	NFSLOCKGSSCL;
	if (clientside && namelen == 0 &&
	    !LIST_EMPTY(&nfsgssclsockhead)) {
		LIST_FOREACH(gcp, &nfsgssclsockhead, nfsgsscl_list) {
			if (gcp->nfsgsscl_uid == uid) {
DEBUG1PRINTF("fnduid\n");
				break;
			}
		}
		if (gcp == LIST_END(&nfsgssclsockhead)) {
			/*
			 * If this is a system Op, such as
			 * Renew and we can't find the
			 * one for nm_uid, all we can
			 * do is use the one that is there,
			 * if it the only one. Since these
			 * Ops aren't particularily harmful,
			 * I don't believe this opens a
			 * security risk. (There is only one,
			 * unless there are multiple users
			 * logged in.)
			 */
			if (((rep->r_flags & R_USEGSSNAME) ||
			     uid == 0) &&
			     LIST_NEXT(LIST_FIRST(&nfsgssclsockhead),
			     nfsgsscl_list) ==
			     LIST_END(&nfsgssclsockhead)) {
DEBUG1PRINTF("fnd1\n");
				gcp = LIST_FIRST(&nfsgssclsockhead);
			}
		}
		if (gcp != LIST_END(&nfsgssclsockhead)) {
			sock = &gcp->nfsgsscl_sock;
			gcp->nfsgsscl_ref++;
		} else {
			NFSUNLOCKGSSCL;
DEBUG1PRINTF("authe0\n");
			return (EAUTH);
		}
	}
	NFSUNLOCKGSSCL;
	if (nfsgss_numgssd == 0 && sock == NULL)
		return (EAUTH);
	if ((handp->nfsh_flag & NFSG_LOCKED) == 0)
		panic("nfs getclhandle");

	/*
	 * ND_GSSINITREPLY tells the rpc layer, this is a GSS initialization.
	 */
	nd = &nfsd;
	nd->nd_flag = ND_GSSINITREPLY;
	cr.cr_gid = 0;
	cr.cr_groups[0] = 0;
	cr.cr_ngroups = 1;

	if (sock == NULL) {
		/*
		 * Choose a gssd in round robin order.
		 */
		next_sock++;
		next_sock %= nfsgss_numgssd;
		sock = &nfsgss_sock[next_sock];
	}

	/*
	 * Start with an initial upcall to the gssd with either a uid
	 * or a principal name.
	 */
	nfsrvd_rephead(nd);
	if (namelen != 0) {
		if (clientside)
			nd->nd_procnum = RPCGSSD_CLINITNAME;
		else
			nd->nd_procnum = RPCGSSD_CLINIT;
		(void) nfsm_strtom(nd, name, namelen);
DEBUG1PRINTF("clinit namelen=%d name=%s\n",namelen,name);
		if (clientside) {
			NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
			sad = NFSSOCKADDR(rep->r_nmp->nm_nam,
			    struct sockaddr_in *);
			*tl = txdr_unsigned(sad->sin_addr.s_addr);
		}
	} else {
DEBUG1PRINTF("inituid=%d\n",uid);
		nd->nd_procnum = RPCGSSD_CLINITUID;
		NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
		*tl++ = txdr_unsigned(uid);
		sad = NFSSOCKADDR(rep->r_nmp->nm_nam, struct sockaddr_in *);
		*tl = txdr_unsigned(sad->sin_addr.s_addr);
	}

	/*
	 * Loop around doing Init and Continue Inits, until done.
	 */
	do {
	    cr.cr_uid = 0;
DEBUG1PRINTF("at gssd upcall proc=%d\n",nd->nd_procnum);
	    /*
	     * Do the initial or continue init upcall to the gssd.
	     */
	    error = newnfs_request(nd, NULL, NULL, sock,
		NULL, NULL, &cr, RPCPROG_GSSD, RPCGSSD_VERS, NULL, 0, NULL);
DEBUG1PRINTF("aft cl gssd upcall err=%d stat=%d\n",error,nd->nd_repstat);
	    if (error) {
		/* If it failed, delete any old handle. */
		if (handb->handlelen > NFSGSS_HANDLESIZ)
			FREE(handb->handle, M_NFSGSSHANDLE);
		handb->handlelen = 0;
		handb->handle = NULL;
		handp->nfsh_flag &= ~(NFSG_INIT | NFSG_CONTINIT);
		if (gcp != NULL) {
			NFSLOCKGSSCL;
			gcp->nfsgsscl_ref--;
			if (gcp->nfsgsscl_ref == 0)
				wakeup((caddr_t)gcp);
			NFSUNLOCKGSSCL;
		}
	    	return (error);
	    }
	    major = nd->nd_repstat;
	    if (major == GSS_S_CONTINUE_NEEDED) {
		/*
		 * For continue needed, the gssd replies with a pointer
		 * to its userland context. Save it for future use.
		 */
	    	NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
	    	len = fxdr_unsigned(int, *tl);
DEBUG1PRINTF("cont needed locallen=%d\n",len);
		if (len != sizeof (void *))
			panic("nfsgetclhandle");
	    	NFSM_DISSECT(cp, u_char *, len);
	    	NFSBCOPY(cp, handp->nfsh_lhandle, len);
DEBUG1PRINTF("retp=0x%x\n",*cp);
	    } else if (major == GSS_S_COMPLETE) {
DEBUG1PRINTF("complete reply\n");
		/*
		 * For Complete, the gssd replies with the session key.
		 */
	    	NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
	    	len = fxdr_unsigned(int, *tl);
	    	/*
	    	 * XXX For now, must be RPCGSS_KEYSIZ in size (a DES key).
	    	 */
	    	if (len != RPCGSS_KEYSIZ)
	    		panic("nfsgss keysize");
	    	NFSM_DISSECT(cp, u_char *, len);
	    	NFSBCOPY(cp, handp->nfsh_key, len);
	    	KEYSCHED(handp->nfsh_key, handp->nfsh_sched);
	    }
	    if ((major == GSS_S_COMPLETE || major == GSS_S_CONTINUE_NEEDED) &&
		srvmajor == GSS_S_CONTINUE_NEEDED) {
		/*
		 * Now, do the Null RPC with the gss token as data.
		 * The gss token is in the rest of the reply from the gssd.
		 */
		newnfs_trimleading(nd);
		nd->nd_mreq = nd->nd_md;
		nd->nd_procnum = NFSPROC_NULL;
		nd->nd_flag |= mechflag;
		if (rep->r_flags & R_USEGSSNAME)
			nd->nd_flag |= ND_USEGSSNAME;
DEBUG1PRINTF("null req ndflag=0x%x\n",nd->nd_flag);
		cr.cr_uid = uid;
		error = newnfs_request(nd, rep->r_nmp, rep->r_clp, rep->r_nrp,
		    NULL, NULL, &cr, rep->r_prog, rep->r_vers, retsum, 0, NULL);
DEBUG1PRINTF("aft null req err=%d\n",error);
		nd->nd_flag &= ~(mechflag | ND_USEGSSNAME);
		if (error) {
			srvmajor = GSS_S_FAILURE;
			nd->nd_mrep = NULL;
		} else {
			/*
			 * Parse the reply, getting the handle and, if
			 * Complete, the window size.
			 */
			NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
			i = fxdr_unsigned(int, *tl);
			if (i > 0) {
			    /*
			     * Handles that are > NFSGSS_HANDLESIZ must
			     * be malloc'd.
			     * First, free any large one that is already
			     * malloc'd here.
			     * If the handle len is already correct, we're
			     * happy campers.
			     */
			    if (handb->handlelen != i) {
				if (handb->handlelen > NFSGSS_HANDLESIZ)
				    FREE(handb->handle, M_NFSGSSHANDLE);
				if (i > NFSGSS_HANDLESIZ)
				    handb->handle = (u_char *)malloc(i,
					M_NFSGSSHANDLE, M_WAITOK);
				else
				    handb->handle = handb->smhandle;
				handb->handlelen = i;
			    }
			    NFSM_DISSECT(cp, u_char *, NFSM_RNDUP(i));
			    NFSBCOPY(cp, handb->handle, i);
			}
			NFSM_DISSECT(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
			srvmajor = fxdr_unsigned(u_int32_t, *tl++);
			if (srvmajor == GSS_S_COMPLETE)
				winsize = fxdr_unsigned(u_int32_t, *++tl);
DEBUG1PRINTF("srvmaj=0x%x wins=%d\n",srvmajor,winsize);
		}
		if (srvmajor == GSS_S_COMPLETE) {
			/*
			 * If Complete, set up the window.
			 */
			handp->nfsh_winsize =
				(winsize > RPCAUTHGSS_SEQWINDOW) ?
				RPCAUTHGSS_SEQWINDOW : winsize;
			handp->nfsh_curwin = handp->nfsh_seqnum =
				arc4random() % 10000;
			handp->nfsh_window = 0;
		}
	    }

	    if (major == GSS_S_CONTINUE_NEEDED) {
		/*
		 * Now build the next gssd request.
		 */
		nd->nd_flag = ND_GSSINITREPLY;
		nfsrvd_rephead(nd);
		NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
		*tl++ = txdr_unsigned(srvmajor);
		*tl = txdr_unsigned(len);
		NFSM_BUILD(cp, u_char *, len);
		NFSBCOPY(handp->nfsh_lhandle, cp, len);
DEBUG1PRINTF("gssd retp=0x%x\n",*cp);
		if (namelen != 0) {
			if (clientside)
				nd->nd_procnum = RPCGSSD_CLCONTNAME;
			else
				nd->nd_procnum = RPCGSSD_CLCONT;
		} else {
			nd->nd_procnum = RPCGSSD_CLCONTUID;
		}
DEBUG1PRINTF("gssd req srvmaj=0x%x loclen=%d\n",srvmajor,len);
		if (srvmajor == GSS_S_COMPLETE ||
		    srvmajor == GSS_S_CONTINUE_NEEDED) {
			newnfs_trimleading(nd);
			mbuf_setnext(nd->nd_mb, nd->nd_md);
			nd->nd_mrep = NULL;
		}
		handp->nfsh_flag &= ~NFSG_INIT;
		handp->nfsh_flag |= NFSG_CONTINIT;
	    } else if (major == GSS_S_COMPLETE) {
		if (srvmajor == GSS_S_COMPLETE) {
		    /*
		     * For Complete, check the verifier.
		     */
		    nfsgss_calcverf(ND_KERBV, handp->nfsh_sched, winsize,
			calcsum);
		    if (NFSBCMP(calcsum, retsum, 8)) {
			srvmajor = GSS_S_FAILURE;
		    } else {
			handp->nfsh_flag &= ~(NFSG_INIT | NFSG_CONTINIT |
			    NFSG_ZAPHANDLE);
			handp->nfsh_flag |= NFSG_COMPLETE;
		    }
DEBUG1PRINTF("srvmajcomp winsize=%d  srvmaj=0x%x\n",winsize,srvmajor);
		}
	    }
	    if (nd->nd_mrep) {
		mbuf_freem(nd->nd_mrep);
		nd->nd_mrep = NULL;
	    }
	} while (major == GSS_S_CONTINUE_NEEDED);
DEBUG1PRINTF("aft loop maj=0x%x\n",major);
	if (major == GSS_S_COMPLETE && srvmajor == GSS_S_COMPLETE) {
		if (gcp != NULL) {
			NFSLOCKGSSCL;
			gcp->nfsgsscl_ref--;
			if (gcp->nfsgsscl_ref == 0)
				wakeup((caddr_t)gcp);
			NFSUNLOCKGSSCL;
		}
		return (0);
	}
	error = EAUTH;
nfsmout:
	if (gcp != NULL) {
		NFSLOCKGSSCL;
		gcp->nfsgsscl_ref--;
		if (gcp->nfsgsscl_ref == 0)
			wakeup((caddr_t)gcp);
		NFSUNLOCKGSSCL;
	}
	if (handb->handlelen > NFSGSS_HANDLESIZ)
		FREE(handb->handle, M_NFSGSSHANDLE);
	handb->handlelen = 0;
	handb->handle = NULL;
	handp->nfsh_flag &= ~(NFSG_INIT | NFSG_CONTINIT);
	if (nd->nd_mrep)
		mbuf_freem(nd->nd_mrep);
	return (error);
}
#endif	/* !__DARWIN_VERS_1050 */

/*
 * Get the next sequence number for a client side GSS.
 */
APPLESTATIC u_int32_t
nfsgss_getclseq(struct nfsgss_handleref *handp)
{

	/*
	 * Wait until within window. Then return current one and increment.
	 */
DEBUG1PRINTF("seq=%d cw=%d\n",handp->nfsh_seqnum,handp->nfsh_curwin);
	while ((handp->nfsh_seqnum - handp->nfsh_curwin) >=
		handp->nfsh_winsize) {
		handp->nfsh_flag |= NFSG_WANTED;
		(void) tsleep((caddr_t)handp, PZERO - 1, "nfsgssseq", hz);
	}
	return (handp->nfsh_seqnum++);
}

/*
 * Advance client seqnum when done with it.
 */
APPLESTATIC void
nfsgss_rcvdclseq(struct nfsreq *rep)
{
	struct nfsreqseq *repseq = NULL;
	u_int64_t newbit;
	u_int32_t diff, seqnum;
	int movedwindow = 0;

	/*
	 * Mark these seqnums done and advance the window, if possible.
	 */
	seqnum = rep->r_seqnum;
	do {
		diff = seqnum - rep->r_handp->nfsh_curwin;
DEBUG1PRINTF("rcv seq=%d cw=%d\n",seqnum,rep->r_handp->nfsh_curwin);
		if (diff > 0 && diff <= RPCAUTHGSS_WINDOW) {
			/*
			 * Set bit to indicate this seq# is now done.
			 * The low order bit refers to nfsh_curwin + 1, the next
			 * bit up refers to nfsh_curwin + 2 and so on...
			 */
			newbit = 0x1;
			newbit <<= (diff - 1);
			rep->r_handp->nfsh_window |= newbit;
		} else if (diff == 0) {
			/*
			 * Advance the window by the next N sequence# already
			 * done, shifting the bits by one each time.
			 */
			while (rep->r_handp->nfsh_window & 0x1) {
			    rep->r_handp->nfsh_curwin++;
			    rep->r_handp->nfsh_window >>= 1;
			}
	
			/*
			 * And advance the window, for nfsh_curwin arriving.
			 */
			rep->r_handp->nfsh_curwin++;
			rep->r_handp->nfsh_window >>= 1;
			movedwindow = 1;
		}
		if (repseq == NULL)
			repseq = SLIST_FIRST(&rep->r_seqlist);
		else
			repseq = SLIST_NEXT(repseq, rs_next);
		if (repseq != SLIST_END(&rep->r_seqlist))
			seqnum = repseq->rs_seqnum;
	} while (repseq != SLIST_END(&rep->r_seqlist));
	if (movedwindow) {
		/*
		 * Wakeup process(es) waiting for the next seq#, as required.
		 */
		if (rep->r_handp->nfsh_flag & NFSG_WANTED) {
			rep->r_handp->nfsh_flag &= ~NFSG_WANTED;
			wakeup((caddr_t)rep->r_handp);
		}
	}
}

/*
 * Find the next seq# used by this RPC, if any.
 * Puts the next seq# in *seqnump.
 * Return 1 if successful, 0 otherwise.
 */
static int
nfsgss_nextclseqnum(struct nfsreq *rep, u_int32_t *seqnump)
{
	struct nfsreqseq *rsp;
	u_int32_t seqnum, tseqnum;

	/*
	 * Just look for a match in the list and return the next one,
	 * if there is one.
	 */
	tseqnum = rep->r_seqnum;
	seqnum = *seqnump;
	rsp = SLIST_FIRST(&rep->r_seqlist);
	while (rsp != SLIST_END(&rep->r_seqlist)) {
		if (tseqnum == seqnum) {
			*seqnump = rsp->rs_seqnum;
			return (1);
		}
		tseqnum = rsp->rs_seqnum;
		rsp = SLIST_NEXT(rsp, rs_next);
	}
	return (0);
}

/*
 * Get a gss handle for the initiator side. Used by the server for
 * callbacks and the client.
 * It only returns with 3 situations:
 * 1 - timeout or termination signal posted on process --> R_SOFTTERM set
 * 2 - authentication error --> R_AUTHERR set
 * 3 - success, with handle ready for data phase use.
 * When getting a context, the handle structure is locked. This lock is
 * released and a ref cnt is incremented (shared lock) before successful
 * return.
 */
APPLESTATIC void
nfsgsscl_gethandle(struct nfsreq *rep)
{
	struct nfsgss_clhandle *chp, *nchp = NULL;
	struct nfsgss_clhandlehead *cheadp;
	int newhandle = 0, namelen = 0, error, clientside = 0;
	u_char *name = NULL;
	uid_t uid = (uid_t)0;
	struct nfsgssclsock *gcp;
	struct timespec ts;

	ts.tv_sec = 1;
	ts.tv_nsec = 0;
	/*
	 * There are 3 cases:
	 * - r_clp != NULL --> callback, with structure in *r_clp
	 * - r_nmp != NULL + ND_USEGSSNAME + nm_krbnamelen --> client side,
	 *   where the principal name in the *r_nmp structure should be used
	 * - r_nmp != NULL without ND_USEGSSNAME --> get one via
	 *   r_uid
	 * If R_TOPLEVEL is not set, the call is a recursive one done
	 * via nfsgss_getclhandle() and lock checking should not be done.
	 */
	if (rep->r_clp != NULL) {
		clientside = 0;
		rep->r_handp = &rep->r_clp->lc_hand;
		rep->r_handb = &rep->r_clp->lc_handlebuf;
		name = rep->r_clp->lc_name;
		namelen = rep->r_clp->lc_namelen;
		uid = (uid_t)0;
	} else if (rep->r_nmp != NULL) {
	    /*
	     * If this is the first system Op,
	     * set the uid for the mount point.
	     * (nm_uid is only used for the
	     *  case where a gsscl is running
	     *  instead of a gssd.)
	     */
	    if (rep->r_flags & R_USEGSSNAME) {
		if (rep->r_nmp->nm_uid == (uid_t)-1)
		    rep->r_nmp->nm_uid = rep->r_uid;
		uid = rep->r_nmp->nm_uid;
	    } else {
		uid = rep->r_uid;
	    }
	    NFSLOCKGSSCL;
	    if (rep->r_nmp->nm_krbnamelen == 0 &&
		((rep->r_flags & R_USEGSSNAME) || uid == 0) &&
		!LIST_EMPTY(&nfsgssclsockhead)) {
		    gcp = LIST_FIRST(&nfsgssclsockhead);
		    if (LIST_NEXT(gcp, nfsgsscl_list) ==
			LIST_END(&nfsgssclsockhead)) {
			/*
			 * This is the oddball case where there is only one
			 * gsscl, so system Ops must use the same handle.
			 * Set uid to the uid for this upcall socket.
			 */
			uid = gcp->nfsgsscl_uid;
		    }
	    }
	    NFSUNLOCKGSSCL;
	    clientside = 1;
	    if (rep->r_nmp->nm_krbnamelen > 0 &&
		(rep->r_flags & R_USEGSSNAME)) {
		NFSLOCKGSSCLHANDLE;
		rep->r_handp = &rep->r_nmp->nm_hand;
		rep->r_handb = &rep->r_nmp->nm_handlebuf;
		name = rep->r_nmp->nm_krbname;
		namelen = rep->r_nmp->nm_krbnamelen;
	    } else {
		/* Malloc now, so we won't need to sleep after the search. */
		if (rep->r_flags & R_TOPLEVEL)
		    MALLOC(nchp, struct nfsgss_clhandle *,
			sizeof (struct nfsgss_clhandle), M_NFSGSSCLHANDLE,
			M_WAITOK);
		NFSLOCKGSSCLHANDLE;
		cheadp = &rep->r_nmp->nm_gssh[nfsgssclhash(uid)];
		LIST_FOREACH(chp, cheadp, nfsgcl_hash) {
		    if (chp->nfsgcl_uid == uid) {
			TAILQ_REMOVE(&rep->r_nmp->nm_gsslru, chp, nfsgcl_lru);
			TAILQ_INSERT_TAIL(&rep->r_nmp->nm_gsslru, chp,
			    nfsgcl_lru);
			if (nchp)
			    FREE((caddr_t)nchp, M_NFSGSSCLHANDLE);
			break;
		    }
		}
		if (chp == LIST_END(cheadp)) {
		    if ((rep->r_flags & R_TOPLEVEL) == 0)
			panic("no uid handle");
		    /*
		     * A new one, so it must be locked and needs to get
		     * a new context.
		     */
		    nchp->nfsgcl_uid = uid;
		    nchp->nfsgcl_hand.nfsh_flag = 0;
		    nchp->nfsgcl_hand.nfsh_refcnt = 0;
		    nchp->nfsgcl_handb.handlelen = 0;
		    nchp->nfsgcl_handb.handle = NULL;
		    LIST_INSERT_HEAD(cheadp, nchp, nfsgcl_hash);
		    TAILQ_INSERT_TAIL(&rep->r_nmp->nm_gsslru, nchp, nfsgcl_lru);
		    nfsgsscl_cnt++;
		    newhandle = 1;
		    chp = nchp;
		}
		rep->r_handp = &chp->nfsgcl_hand;
		rep->r_handb = &chp->nfsgcl_handb;
		name = NULL;
		namelen = 0;
	    }
	}

	/*
	 * Now, if it isn't a new new handle, we must wait for any lock to be
	 * released.
	 * If we need to get a context, we must also wait for refcnt == 0.
	 */
	if (rep->r_flags & R_TOPLEVEL) {
		if (newhandle == 0) {
			while ((rep->r_handp->nfsh_flag & NFSG_LOCKED) ||
			    ((rep->r_handp->nfsh_flag & (NFSG_COMPLETE |
			      NFSG_ZAPHANDLE)) != NFSG_COMPLETE &&
			     rep->r_handp->nfsh_refcnt > 0)) {
				rep->r_handp->nfsh_flag |= NFSG_WANTED;
				if (clientside)
				 (void)nfsmsleep((caddr_t)&rep->r_handp->nfsh_flag,
				    NFSGSSCLHANDLEMUTEXPTR, PZERO, "nfsgshdl",
				    &ts);
				else
				 (void)tsleep((caddr_t)&rep->r_handp->nfsh_flag,
				    PZERO, "nfsgshdl", hz);
				if (rep->r_flags & R_SOFTTERM) {
					rep->r_handp = NULL;
					if (clientside)
						NFSUNLOCKGSSCLHANDLE;
					return;
				}
			}
			if (rep->r_handp->nfsh_flag & NFSG_ZAPHANDLE) {
				if (rep->r_handb->handlelen > NFSGSS_HANDLESIZ)
					FREE(rep->r_handb->handle,
					    M_NFSGSSHANDLE);
				rep->r_handb->handlelen = 0;
				rep->r_handb->handle = NULL;
				rep->r_handp->nfsh_flag &= ~(NFSG_COMPLETE |
				    NFSG_ZAPHANDLE);
			}
		}

		/*
		 * Now, we can go ahead. If NFSG_COMPLETE is not set, we must
		 * lock the handle and get the context. Otherwise, we can just
		 * get a refcnt.
		 */
		if ((rep->r_handp->nfsh_flag & NFSG_COMPLETE) == 0) {
			rep->r_handp->nfsh_flag |= (NFSG_INIT | NFSG_LOCKED);
			if (clientside)
				NFSUNLOCKGSSCLHANDLE;
			error = nfsgss_getclhandle(rep, name, namelen, uid,
			    ND_KERBV, clientside);
			if (clientside)
				NFSLOCKGSSCLHANDLE;
			nfsgsscl_releasehandle(rep, 1);
			if (error) {
				rep->r_handp = NULL;
				if (clientside)
					NFSUNLOCKGSSCLHANDLE;
				NFSLOCKREQ();
				rep->r_flags |= R_AUTHERR;
				NFSUNLOCKREQ();
				return;
			}
		}
		rep->r_handp->nfsh_refcnt++;
		if (rep->r_nmp)
			nfsgsscl_trim(rep->r_nmp);
	}
	if (clientside)
		NFSUNLOCKGSSCLHANDLE;
}

/*
 * Release a context handle.
 */
APPLESTATIC void
nfsgsscl_releasehandle(struct nfsreq *rep, int haslock)
{
	struct nfsgss_handleref *handp = rep->r_handp;

	if (!haslock && (rep->r_flags & R_CLIENTSIDE))
		NFSLOCKGSSCLHANDLE;
	if (handp->nfsh_flag & NFSG_LOCKED) {
		handp->nfsh_flag &= ~NFSG_LOCKED;
		if (handp->nfsh_flag & NFSG_WANTED) {
			handp->nfsh_flag &= ~NFSG_WANTED;
			wakeup((caddr_t)&handp->nfsh_flag);
		}
	} else {
		if (handp->nfsh_refcnt == 0)
			panic("nfsgsscl ref 0");
		handp->nfsh_refcnt--;
		if (handp->nfsh_refcnt == 0 &&
		    (handp->nfsh_flag & NFSG_WANTED)) {
			handp->nfsh_flag &= ~NFSG_WANTED;
			wakeup((caddr_t)&handp->nfsh_flag);
		}
	}
	if (!haslock && (rep->r_flags & R_CLIENTSIDE))
		NFSUNLOCKGSSCLHANDLE;
}

/*
 * Trim the client handle list size down.
 */
static void
nfsgsscl_trim(struct nfsmount *nmp)
{
	struct nfsgss_clhandle *chp, *nchp;

	chp = TAILQ_FIRST(&nmp->nm_gsslru);
	while (chp != TAILQ_END(&nmp->nm_gsslru) &&
	    nfsgsscl_cnt > nfsgsscl_highwater) {
		nchp = TAILQ_NEXT(chp, nfsgcl_lru);
		if (chp->nfsgcl_hand.nfsh_refcnt == 0 &&
		    (chp->nfsgcl_hand.nfsh_flag & (NFSG_LOCKED | NFSG_WANTED))
		     == 0)
			nfsgsscl_freehandle(nmp, chp);
		chp = nchp;
	}
}

/*
 * Free up a client handle structure.
 */
APPLESTATIC void
nfsgsscl_freehandle(struct nfsmount *nmp, struct nfsgss_clhandle *chp)
{

	TAILQ_REMOVE(&nmp->nm_gsslru, chp, nfsgcl_lru);
	LIST_REMOVE(chp, nfsgcl_hash);
	if (chp->nfsgcl_handb.handlelen > NFSGSS_HANDLESIZ)
		FREE(chp->nfsgcl_handb.handle, M_NFSGSSHANDLE);
	FREE((caddr_t)chp, M_NFSGSSCLHANDLE);
	nfsgsscl_cnt--;
}

/*
 * This function marks a context in a handle no longer valid, so the
 * next call to nfsgsscl_gethandle() will try and get a new context.
 */
APPLESTATIC void
nfsgsscl_zaphandle(struct nfsreq *rep)
{

	if ((rep->r_flags & R_TOPLEVEL) == 0)
		panic("nfs gss zap not toplevel");

	rep->r_handp->nfsh_flag |= NFSG_ZAPHANDLE;
}

